Skip to content

Method: cloneCollectionContent(Object, Field, Collection, Collection, CloneConfiguration)

1: /**
2: * Copyright (C) 2016 Czech Technical University in Prague
3: *
4: * This program is free software: you can redistribute it and/or modify it under
5: * the terms of the GNU General Public License as published by the Free Software
6: * Foundation, either version 3 of the License, or (at your option) any
7: * later version.
8: *
9: * This program is distributed in the hope that it will be useful, but WITHOUT
10: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11: * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12: * details. You should have received a copy of the GNU General Public License
13: * along with this program. If not, see <http://www.gnu.org/licenses/>.
14: */
15: package cz.cvut.kbss.jopa.sessions;
16:
17: import cz.cvut.kbss.jopa.adapters.IndirectCollection;
18: import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException;
19: import cz.cvut.kbss.jopa.model.annotations.Types;
20: import cz.cvut.kbss.jopa.model.metamodel.PluralAttribute;
21: import cz.cvut.kbss.jopa.utils.CollectionFactory;
22: import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils;
23: import cz.cvut.kbss.jopa.utils.MetamodelUtils;
24:
25: import java.lang.reflect.Constructor;
26: import java.lang.reflect.Field;
27: import java.lang.reflect.InvocationTargetException;
28: import java.security.AccessController;
29: import java.security.PrivilegedActionException;
30: import java.util.*;
31:
32: /**
33: * Special class for cloning collections. Introduced because some Java collection have no no-argument constructor and
34: * thus they must be cloned specially. NOTE: This class may be removed in case a better cloning mechanisms (namely
35: * database mappings and copy policies) is introduced.
36: */
37: class CollectionInstanceBuilder extends AbstractInstanceBuilder {
38:
39: private static final Class<?> singletonListClass = Collections.singletonList(null).getClass();
40: private static final Class<?> singletonSetClass = Collections.singleton(null).getClass();
41: private static final Class<?> arrayAsListClass = Arrays.asList(new Object()).getClass();
42:
43: private static final Class<? extends List> DEFAULT_LIST_CLASS = ArrayList.class;
44: private static final Class<? extends Set> DEFAULT_SET_CLASS = HashSet.class;
45:
46: CollectionInstanceBuilder(CloneBuilderImpl builder, UnitOfWork uow) {
47: super(builder, uow);
48: this.populates = true;
49: }
50:
51: /**
52: * This method is the entry point for cloning the Java collections. It clones standard collections as well as
53: * immutable collections and singleton collections. </p>
54: * <p>
55: * Currently supported are List and Set.
56: *
57: * @param collection The collection to clone
58: * @return A deep clone of the specified collection
59: */
60: @Override
61: Object buildClone(Object cloneOwner, Field field, Object collection, CloneConfiguration configuration)
62: throws OWLPersistenceException {
63: assert collection instanceof Collection;
64: Collection<?> container = (Collection<?>) collection;
65: if (container instanceof IndirectCollection<?>) {
66: container = (Collection<?>) ((IndirectCollection<?>) container).getReferencedCollection();
67: }
68: if (Collections.emptyList() == container) {
69: return Collections.emptyList();
70: }
71: if (Collections.emptySet() == container) {
72: return Collections.emptySet();
73: }
74: Collection<?> clone = cloneUsingDefaultConstructor(cloneOwner, field, container, configuration);
75: if (clone == null) {
76: clone = buildInstanceOfSpecialCollection(cloneOwner, field, container, configuration);
77: }
78: if (clone == null) {
79: clone = buildDefaultCollectionInstance(cloneOwner, field, container, configuration);
80: }
81: clone = (Collection<?>) builder.createIndirectCollection(clone, cloneOwner, field);
82: return clone;
83: }
84:
85: /**
86: * Clones the specified collection using its default zero argument constructor. If the specified collection has none
87: * (e. g. like SingletonList), this method returns null.
88: *
89: * @param container The collection to clone.
90: * @return cloned collection
91: */
92: private Collection<?> cloneUsingDefaultConstructor(Object cloneOwner, Field field,
93: Collection<?> container, CloneConfiguration configuration) {
94: Class<?> javaClass = container.getClass();
95: Collection<?> result = createNewInstance(javaClass, container.size());
96: if (result != null) {
97: // Makes shallow copy
98: cloneCollectionContent(cloneOwner, field, container, result, configuration);
99: }
100: return result;
101: }
102:
103: private Collection<?> createNewInstance(Class<?> type, int size) {
104: Object[] params = null;
105: Class<?>[] types = {int.class};
106: // Look for constructor taking initial size as parameter
107: Constructor<?> ctor = getDeclaredConstructorFor(type, types);
108: if (ctor != null) {
109: params = new Object[1];
110: params[0] = size;
111: } else {
112: ctor = DefaultInstanceBuilder.getDeclaredConstructorFor(type, null);
113: }
114: if (ctor == null) {
115: return null;
116: }
117: Collection<?> result = null;
118: try {
119: result = (Collection<?>) ctor.newInstance(params);
120: } catch (InstantiationException | InvocationTargetException | IllegalArgumentException e) {
121: throw new OWLPersistenceException(e);
122: } catch (IllegalAccessException e) {
123: logConstructorAccessException(ctor, e);
124: try {
125: result = (Collection<?>) AccessController
126: .doPrivileged(new PrivilegedInstanceCreator(ctor));
127: } catch (PrivilegedActionException ex) {
128: logPrivilegedConstructorAccessException(ctor, ex);
129: // Do nothing
130: }
131: }
132: return result;
133: }
134:
135: /**
136: * Clone all the elements in the collection. This will make sure that the cloning process creates a deep copy.
137: *
138: * @param source The collection to clone.
139: */
140: private void cloneCollectionContent(Object cloneOwner, Field field, Collection<?> source,
141: Collection<?> target, CloneConfiguration configuration) {
142:• if (source.isEmpty()) {
143: return;
144: }
145: Collection<Object> tg = (Collection<Object>) target;
146:• for (Object elem : source) {
147:• if (elem == null) {
148: tg.add(null);
149: continue;
150: }
151:• if (CloneBuilderImpl.isImmutable(elem.getClass())) {
152: tg.addAll(source);
153: break;
154: }
155: tg.add(cloneCollectionElement(cloneOwner, field, elem, configuration));
156: }
157: }
158:
159: private Object cloneCollectionElement(Object cloneOwner, Field field, Object element,
160: CloneConfiguration configuration) {
161: Object clone;
162: if (builder.isTypeManaged(element.getClass())) {
163: clone = uow.registerExistingObject(element, configuration.getDescriptor(), configuration.getPostRegister());
164: } else {
165: clone = builder.buildClone(cloneOwner, field, element, configuration.getDescriptor());
166: }
167: return clone;
168: }
169:
170:
171: private Collection<?> buildInstanceOfSpecialCollection(Object cloneOwner, Field field, Collection<?> container,
172: CloneConfiguration configuration) {
173: Collection<?> clone;
174: Constructor<?> c;
175: Object[] params = new Object[1];
176: if (arrayAsListClass.isInstance(container)) {
177: final List<?> arrayList = new ArrayList<>(container.size());
178: cloneCollectionContent(cloneOwner, field, container, arrayList, configuration);
179: c = getFirstDeclaredConstructorFor(ArrayList.class);
180: params[0] = arrayList;
181: } else {
182: if (singletonListClass.isInstance(container)) {
183: c = getFirstDeclaredConstructorFor(singletonListClass);
184: } else if (singletonSetClass.isInstance(container)) {
185: c = getFirstDeclaredConstructorFor(singletonSetClass);
186: } else {
187: return null;
188: }
189: final Object element = container.iterator().next();
190: params[0] = CloneBuilderImpl.isImmutable(element.getClass()) ? element :
191: cloneCollectionElement(cloneOwner, field, element, configuration);
192: }
193: try {
194: if (!c.isAccessible()) {
195: c.setAccessible(true);
196: }
197: clone = (Collection<?>) c.newInstance(params);
198: } catch (InstantiationException | IllegalArgumentException | InvocationTargetException e) {
199: throw new OWLPersistenceException(e);
200: } catch (IllegalAccessException e) {
201: logConstructorAccessException(c, e);
202: try {
203: clone = (Collection<?>) AccessController.doPrivileged(new PrivilegedInstanceCreator(c));
204: } catch (PrivilegedActionException ex) {
205: throw new OWLPersistenceException(ex);
206: }
207: }
208: return clone;
209: }
210:
211: private Collection<?> buildDefaultCollectionInstance(Object cloneOwner, Field field, Collection<?> container,
212: CloneConfiguration configuration) {
213: LOG.trace("Unable to find matching collection constructor. Creating default collection.");
214: final Collection<?> clone;
215: try {
216: if (container instanceof List) {
217: clone = DEFAULT_LIST_CLASS.newInstance();
218: } else if (container instanceof Set) {
219: clone = DEFAULT_SET_CLASS.newInstance();
220: } else {
221: throw new OWLPersistenceException(
222: "Cannot clone unsupported collection instance of type " + container.getClass() + ".");
223: }
224: cloneCollectionContent(cloneOwner, field, container, clone, configuration);
225: } catch (InstantiationException | IllegalAccessException e) {
226: throw new OWLPersistenceException(e);
227: }
228: return clone;
229: }
230:
231: @Override
232: void mergeChanges(Field field, Object target, Object originalValue, Object cloneValue) {
233: assert originalValue == null || originalValue instanceof Collection;
234: assert cloneValue instanceof Collection;
235:
236: Collection<Object> clone = (Collection<Object>) cloneValue;
237: if (clone instanceof IndirectCollection) {
238: clone = ((IndirectCollection<Collection<Object>>) clone).getReferencedCollection();
239: }
240: Collection<Object> orig = (Collection<Object>) createNewInstance(clone.getClass(), clone.size());
241: if (orig == null) {
242: orig = createDefaultCollection(clone.getClass());
243: }
244: EntityPropertiesUtils.setFieldValue(field, target, orig);
245:
246: if (clone.isEmpty()) {
247: return;
248: }
249: for (Object cl : clone) {
250: orig.add(uow.contains(cl) ? builder.getOriginal(cl) : cl);
251: }
252: final Types types = field.getAnnotation(Types.class);
253: if (types != null) {
254: MetamodelUtils.checkForModuleSignatureExtension(orig, builder.getMetamodel());
255: }
256: }
257:
258: private static Collection<Object> createDefaultCollection(Class<?> cls) {
259: return CollectionFactory.createDefaultCollection(PluralAttribute.CollectionType.fromClass(cls));
260: }
261: }